#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/pwm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define MISC_NAME "electric_engine_2"
#define PWM_NAME  "pwm_2"
#define PLATFORM_NAME "pwm_misc_platform_2"

static const struct of_device_id of_pwm_match[] = {
    {.compatible = "electric-engine-2"},
    {}
};

struct pwmset
{
    int freq;
    int cnt;
};
struct pwmset testpwmset;

struct pwminfo
{
    int pwm_id;
    unsigned int period;
    unsigned int level;
    unsigned int duty;
    struct pwm_device *pwm;
}; 
struct pwminfo _pwminfo;


#define MEMDEV_IOC_MAGIC  'k'
#define PWMON                   _IOW(MEMDEV_IOC_MAGIC, 0, struct pwmset*)
#define PWMOFF                  _IO(MEMDEV_IOC_MAGIC, 1)
#define GETINFO                  _IOR(MEMDEV_IOC_MAGIC, 2,struct pwmset* )


static long pwm_ioctl(struct file *file,        
                 unsigned int cmd,      
                 unsigned long arg)
{
    int ret; 
    struct pwmset _pwmset;
    void __user *argp = (void __user *) arg;
    switch(cmd) {
        case PWMON: 
                if (argp == NULL) 
                {
                    printk("pwm ioctl failed");
                    return -EINVAL;
                }
                if (copy_from_user(&_pwmset, argp, sizeof(struct pwmset)))
                {
                    printk("pwmset copy failed");
                    return -EINVAL;
                }

                // printk("%d\n",_pwmset.cnt);
                // printk("%d\n",_pwmset.freq);
                // printk("%d\n", _pwminfo.pwm);
                ret = pwm_config(_pwminfo.pwm,_pwmset.cnt,_pwmset.freq);
                if(ret < 0){
                    printk("pwm ioctl fail");
                    return 0;
                }
                pwm_enable(_pwminfo.pwm);
            break;
        case PWMOFF:
                    ret = pwm_config(_pwminfo.pwm,0,500000);
                    if(ret < 0){
                        printk("pwm ioctl fail");
                        return 0;
                    }
                    pwm_disable(_pwminfo.pwm);
 
            break;
        case GETINFO:
                if (copy_to_user(argp, &_pwmset, sizeof(struct pwmset)))
                {
                    printk("pwmset copy failed");
                    return -EINVAL;
                }
            break;
        default:
                 printk("ioctl somethings wrong");
                 break;
    }
    return 0;
}

static struct file_operations misc_fops={
    .owner = THIS_MODULE,
    .unlocked_ioctl = pwm_ioctl,
};

static struct miscdevice misc_dev={
    .minor = MISC_DYNAMIC_MINOR,
    .name = MISC_NAME,
    .fops = &misc_fops,
};


static int pwm_probe(struct platform_device *pdev)
{
    printk("match success\n");
    struct device_node *node = pdev->dev.of_node;

    int ret;

    if (!node) {
        dev_err(&pdev->dev, "DT node missing\n");
        return ret;
    }
    
    ret = of_property_read_u32(node, "pwm-id", &_pwminfo.pwm_id);
    if (ret < 0) {
        dev_err(&pdev->dev, "pwm-id missing\n");
        return ret;
    }

    ret = of_property_read_u32(node, "period", &_pwminfo.period);
    if (ret < 0) {
        dev_err(&pdev->dev, "lperiod missing\n");
        return ret;
    }
    
    ret = of_property_read_u32(node, "default-level", &_pwminfo.level);
    if (ret < 0) {
        dev_err(&pdev->dev, "default-level missing\n");
        return ret;
    }

    
    _pwminfo.pwm = pwm_request(_pwminfo.pwm_id, PWM_NAME);
    if (IS_ERR(_pwminfo.pwm)) {
        dev_err(&pdev->dev, "pwm request failed\n");
        ret = PTR_ERR(_pwminfo.pwm);
        return ret;
    }
    
    
     printk("%d\n", _pwminfo.pwm_id);     
     printk("%d\n", _pwminfo.period);  
    _pwminfo.duty = _pwminfo.level * _pwminfo.period / 100;
     printk("%d\n", _pwminfo.duty);


    ret = pwm_config(_pwminfo.pwm, _pwminfo.period, _pwminfo.period);
    if(ret < 0){
        dev_err(&pdev->dev, "init filed PWM\n");
        return ret;
    }


     misc_register(&misc_dev);
     printk("misc_dev success\n");

    return 0;
}

static int pwm_remove(struct platform_device *pdev)
{
    pwm_disable(_pwminfo.pwm);
    pwm_free(_pwminfo.pwm);
    
    misc_deregister(&misc_dev);

    return 0;
}


static struct platform_driver pwm_diver = {
    .probe = pwm_probe,
    .remove = pwm_remove,
    .driver = {
        .name = PLATFORM_NAME,
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(of_pwm_match),
    },
};

module_platform_driver(pwm_diver);

MODULE_DESCRIPTION("pwm platform driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZCX-HDU");



